import {
  BadRequestException,
  Injectable,
  UnauthorizedException,
} from '@nestjs/common';
import { compare, hash } from 'bcrypt';
import { plainToInstance } from 'class-transformer';

// import { AppLogger } from '../../shared/logger/logger.service';
// import { RequestContext } from '../../shared/request-context/request-context.dto';
import { CreateUserInput } from '../dtos/user-create-input.dto';
import { UserOutput } from '../dtos/user-output.dto';
import { UpdateUserInput } from '../dtos/user-update-input.dto';
import { User } from '../entities/user.entity';
import { UserRepository } from '../repositories/user.repository';
import { RequestContext } from 'src/shared/request-context/request-context.dto';
import { UpdateUserPassword } from '../dtos/user-update-password.dto';

@Injectable()
export class UserService {
  constructor(
    private repository: UserRepository,
    // private readonly logger: AppLogger,
  ) {
    // this.logger.setContext(UserService.name);
  }
  async createUser(
    ctx: RequestContext,
    input: CreateUserInput,
  ): Promise<UserOutput> {
    // this.logger.log(ctx, `${this.createUser.name} was called`);

    const user = plainToInstance(User, input);

    user.password = await hash(input.password, 10);

    // this.logger.log(ctx, `calling ${UserRepository.name}.saveUser`);
    await this.repository.save(user);

    return plainToInstance(UserOutput, user, {
      excludeExtraneousValues: true,
    });
  }

  async validateUsernamePassword(
    ctx: RequestContext,
    username: string,
    pass: string,
  ): Promise<UserOutput> {
    // this.logger.log(ctx, `${this.validateUsernamePassword.name} was called`);

    // this.logger.log(ctx, `calling ${UserRepository.name}.findOne`);
    const user = await this.repository.findOne({ where: { username } });
    if (!user) throw new UnauthorizedException();

    const match = await compare(pass, user.password);
    if (!match) throw new UnauthorizedException();

    return plainToInstance(UserOutput, user, {
      excludeExtraneousValues: true,
    });
  }

  async getUsers(
    ctx: RequestContext,
    limit: number,
    offset: number,
  ): Promise<{ users: UserOutput[]; count: number }> {
    // this.logger.log(ctx, `${this.getUsers.name} was called`);

    // this.logger.log(ctx, `calling ${UserRepository.name}.findAndCount`);
    const [users, count] = await this.repository.findAndCount({
      where: {},
      take: limit,
      skip: offset,
    });

    const usersOutput = plainToInstance(UserOutput, users, {
      excludeExtraneousValues: true,
    });

    return { users: usersOutput, count };
  }

  async findById(ctx: RequestContext, id: number): Promise<UserOutput> {
    // this.logger.log(ctx, `${this.findById.name} was called`);

    // this.logger.log(ctx, `calling ${UserRepository.name}.findOne`);
    const user = await this.repository.findOne({ where: { id } });

    return plainToInstance(UserOutput, user, {
      excludeExtraneousValues: true,
    });
  }

  async getUserById(ctx: RequestContext, id: number): Promise<UserOutput> {
    // this.logger.log(ctx, `${this.getUserById.name} was called`);

    // this.logger.log(ctx, `calling ${UserRepository.name}.getById`);
    const user = await this.repository.getById(id);

    return plainToInstance(UserOutput, user, {
      excludeExtraneousValues: true,
    });
  }

  async findAllUserInfoById(ctx: RequestContext, id: number): Promise<User> {
    // this.logger.log(ctx, `${this.findById.name} was called`);

    // this.logger.log(ctx, `calling ${UserRepository.name}.findOne`);
    const user = await this.repository.findOne({ where: { id } });

    return user!;
  }

  async findByUsername(
    ctx: RequestContext,
    username: string,
  ): Promise<UserOutput> {
    // this.logger.log(ctx, `${this.findByUsername.name} was called`);

    // this.logger.log(ctx, `calling ${UserRepository.name}.findOne`);
    const user = await this.repository.findOne({ where: { username } });

    return plainToInstance(UserOutput, user, {
      excludeExtraneousValues: true,
    });
  }

  async updateUser(
    ctx: RequestContext,
    userId: number,
    input: UpdateUserInput,
  ): Promise<UserOutput> {
    // this.logger.log(ctx, `${this.updateUser.name} was called`);

    // Hash the password if it exists in the input payload.
    if (input.password) {
      input.password = await hash(input.password, 10);
    }

    if (input.newPassword) {
      input.password = await hash(input.newPassword, 10);
    }

    const oldUser = await this.findAllUserInfoById(ctx, ctx.user.id);
    if (oldUser.password !== input.password) {
      throw new BadRequestException('旧密码不正确');
    }

    // merges the input (2nd line) to the found user (1st line)
    const updatedUser: User = {
      ...oldUser,
      ...plainToInstance(User, input),
    };

    // this.logger.log(ctx, `calling ${UserRepository.name}.save`);
    await this.repository.save(updatedUser);

    return plainToInstance(UserOutput, updatedUser, {
      excludeExtraneousValues: true,
    });
  }

  async updateUserPassword(
    ctx: RequestContext,
    input: UpdateUserPassword,
  ): Promise<{ code: 200 | 209; msg: string }> {
    // this.logger.log(ctx, `${this.updateUser.name} was called`);

    // Hash the password if it exists in the input payload.
    // if (input.password) {
    //   input.password = await hash(input.password, 10);
    // }

    const oldUser = await this.findAllUserInfoById(ctx, ctx.user.id);
    const match = await compare(input.password, oldUser.password);
    if (!match) {
      return {
        code: 209,
        msg: '旧密码不正确',
      };
    }

    // Hash the password if it exists in the input payload.
    if (input.newPassword) {
      input.newPassword = await hash(input.newPassword, 10);
    }

    // merges the input (2nd line) to the found user (1st line)
    const updatedUser: User = {
      ...oldUser,
      password: input.newPassword,
    };

    // this.logger.log(ctx, `calling ${UserRepository.name}.save`);
    await this.repository.save(updatedUser);

    return {
      code: 200,
      msg: 'update successfull !',
    };
  }
}
